package com.data.service;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.data.pojo.AnnualProfit;
import com.data.pojo.Profit;
import com.data.pojo.StockData;
import com.data.pojo.Trade;
import com.data.util.StockUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.*;

import cn.hutool.core.collection.CollUtil;

/**
 * @Author Str
 * @Date 2021/11/24 14:33
 * @Description :股票数据服务
 */
@Service
public class StockDataService {

    @Autowired
    private StockUtil stockUtil;


    /**
     * 将股票数据存入云服务器
     * <p>
     *
     * @param stockCode 字段是由市场类型和股票代码组成。
     *                  第一位如果是0，代表股票为沪市的；
     *                  如果是1，则代表是深市的。之后跟着的6位则是股票代码。
     * @return java.io.File
     * @SuppressWarnings("finally") 用于抑制finally产生警告信息
     */
    @Cacheable(cacheNames = "stock_data", key = "#p0")
    public List<StockData> getStockData(String stockCode) {
        return CollUtil.toList();
    }

    /**
     * 提交数据到redis
     *
     * @param code      股票代码
     * @param startTime 起始时间
     * @param endTime   结束时间
     * @return java.util.List<com.data.pojo.StockData>
     */
    @CachePut(cacheNames = "stock_data", key = "#p0")
    public List<StockData> putStockData(String code, String startTime, String endTime) {
        return stockUtil.getStockDate(code, startTime, endTime);
    }


    /**
     * 根据key删除redis中对应的数据
     *
     * @param code
     * @return void
     */
    @CacheEvict(cacheNames = "stock_data", key = "#p0")
    public void remove(String code) {
    }

    /**
     * 用于获取模拟趋势交易的收益
     * <p>
     * 假设是20日均线，买点为超过均线1.01倍均线，卖点为低于0.99倍均线，
     * 当前取出的股票价格（closePoint）为604，均线（avg）为581，过去20日的最大值（max）为606
     * 可以计算出当前的增长率（increase_rate）为1.039，亏损率（decrease_rate）为0.996
     * 增长率>买点，进行买操作
     *
     * @param ma            （moving average）移动均线，可以设定为10、20、60日的均线
     * @param sellRate      卖出阈值，也就是低于均线的倍数，一般是0.99、0.98...
     * @param buyRate       购买阈值，也就是超过均线的倍数，一般是1.01、1.02...
     * @param serviceCharge 每笔交易的手续费
     * @param stockDataList 指定的股票数据
     * @return java.util.Map<java.lang.String       ,       java.lang.Object>
     */
    public Map<String, Object> simulate(int ma, float sellRate, float buyRate, float serviceCharge, List<StockData> stockDataList) {

        List<Profit> profits = new ArrayList<>();  // 利润记录
        List<Trade> trades = new ArrayList<>();  // 交易记录
        float initCash = 1000;  //  设置初始金额
        float cash = initCash;  // 现金
        float share = 0;  // 股票份额
        float value = 0;  // 资产价值

        // 交易统计数据
        int winCount = 0;  // 成功次数
        float totalWinRate = 0;  // 总体成功率
        float avgWinRate = 0;  // 平均成功率
        int lossCount = 0;  // 失败次数
        float totalLossRate = 0;  // 总体失败率
        float avgLossRate = 0;  // 平均失败率

        float init = 0;  // 先获取当前股票数据开始日期的价格
        float end = 0;  // 获取当前股票数据结束日期的价格
        Integer recommendedLevel = 0;
        if (!stockDataList.isEmpty())
            init = stockDataList.get(0).getClosingPrice();
        end = stockDataList.get(stockDataList.size() - 1).getClosingPrice();

        for (int i = 0; i < stockDataList.size(); i++) {
            StockData stockData = stockDataList.get(i);
            // 获取当前收盘价格
            float closePoint = stockData.getClosingPrice();
            // 获取过去的均线
            float avg = getMA(i, ma, stockDataList);
            // 获取过去的最大值
            float max = getMax(i, ma, stockDataList);

            // 计算当前的增长率
            float increase_rate = closePoint / avg;
            // 计算当前的亏损率
            float decrease_rate = closePoint / max;
            // 计算推荐等级
            recommendedLevel = calculateRecommendedLevel(end, avg);

            // 因为均线要取的是当前日期的前面的数据，当该点没有数据或前面的数据不够，就认定均线为0，不进行校验，
            // 所以前均线数值的天数无法进行预测，均线看起来就是直的
            if (avg != 0) {
                // 均线增长率超过了设定的买点
                if (increase_rate > buyRate) {
                    // 如果没买，就进行买操作
                    if (0 == share) {
                        // 计算份额
                        share = cash / closePoint;
                        // 现金置为0
                        cash = 0;

                        // 购买时的记录
                        Trade trade = new Trade();
                        trade.setBuyDate(stockData.getDate());
                        trade.setBuyClosePoint(stockData.getClosingPrice());
                        trade.setSellDate("n/a");
                        trade.setSellClosePoint(0);
                        trades.add(trade);
                    }
                }
                // 均线亏损率低于了设定的卖点
                else if (decrease_rate < sellRate) {
                    // 如果没卖，就进行卖操作
                    if (0 != share) {
                        // 计算现金
                        cash = closePoint * share * (1 - serviceCharge);
                        // 份额置为0
                        share = 0;

                        // 卖出时的记录
                        Trade trade = trades.get(trades.size() - 1);
                        trade.setSellDate(stockData.getDate());
                        trade.setSellClosePoint(stockData.getClosingPrice());

                        float rate = cash / initCash;
                        trade.setRate(rate);

                        // 计算成功和失败信息
                        if (trade.getSellClosePoint() - trade.getBuyClosePoint() > 0) {
                            totalWinRate += (trade.getSellClosePoint() - trade.getBuyClosePoint()) / trade.getBuyClosePoint();
                            winCount++;
                        } else {
                            totalLossRate += (trade.getSellClosePoint() - trade.getBuyClosePoint()) / trade.getBuyClosePoint();
                            lossCount++;
                        }
                    }
                }

            }

            if (share != 0) {
                // 计算买入后的资产价值
                value = closePoint * share;
            } else {
                // 没买就为现金价值
                value = cash;
            }
            // 计算资产的增长率情况
            float rate = value / initCash;

            // 获取利润情况
            Profit profit = new Profit();
            profit.setDate(stockData.getDate());
            profit.setValue(rate * init);

            profits.add(profit);

        }

        // 计算平均成功和失败率
        avgWinRate = totalWinRate / winCount;
        avgLossRate = totalLossRate / lossCount;

        List<AnnualProfit> annualProfits = calculateAnnualProfits(stockDataList, profits);


        Map<String, Object> map = new HashMap<>();
        map.put("profits", profits);
        map.put("trades", trades);

        map.put("winCount", winCount);
        map.put("lossCount", lossCount);
        map.put("avgWinRate", avgWinRate);
        map.put("avgLossRate", avgLossRate);

        map.put("annualProfits", annualProfits);
        System.out.println(recommendedLevel+"===========================");

        map.put("recommendedLevel", recommendedLevel);

        return map;
    }

    /**
     * 获取过去指定均线日期的最大值，比如找到过去20日的最大值
     *
     * @param i    股票数据集合中的第几个数据
     * @param day  指定均线的多少日期时间
     * @param list 股票数据集合
     * @return float
     */
    private static float getMax(int i, int day, List<StockData> list) {
        int start = i - 1 - day;
        if (start < 0)
            start = 0;
        int now = i - 1;

        if (start < 0)
            return 0;

        float max = 0;
        for (int j = start; j < now; j++) {
            StockData bean = list.get(j);
            if (bean.getClosingPrice() > max) {
                max = bean.getClosingPrice();
            }
        }
        return max;
    }

    /**
     * 计算MA均线
     *
     * @param i    股票数据集合中的第几个数据
     * @param ma   指定多少日的均线
     * @param list 股票数据集合
     * @return float
     */
    private static float getMA(int i, int ma, List<StockData> list) {
        int start = i - 1 - ma;
        int now = i - 1;

        if (start < 0)
            return 0;

        float sum = 0;
        float avg = 0;
        for (int j = start; j < now; j++) {
            StockData bean = list.get(j);
            sum += bean.getClosingPrice();
        }
        avg = sum / (now - start);
        return avg;
    }

    /**
     * 计算当前的时间范围是多少年
     *
     * @param stockDataList
     * @return float
     */
    public float getYear(List<StockData> stockDataList) {
        float years;
        String sDateStart = stockDataList.get(0).getDate();
        String sDateEnd = stockDataList.get(stockDataList.size() - 1).getDate();

        Date dateStart = DateUtil.parse(sDateStart);
        Date dateEnd = DateUtil.parse(sDateEnd);

        long days = DateUtil.between(dateStart, dateEnd, DateUnit.DAY);
        years = days / 365f;
        return years;
    }

    /**
     * 用于获取某个日期的年份
     *
     * @param date
     * @return int
     */
    private int getYear(String date) {
        String strYear = StrUtil.subBefore(date, "-", false);
        return Convert.toInt(strYear);
    }

    /**
     * 计算某一年的股票投资收益
     *
     * @param year          年份
     * @param stockDataList 股票数据
     * @return float
     */
    private float getStockIncome(int year, List<StockData> stockDataList) {
        StockData first = null;
        StockData last = null;
        for (StockData stockData : stockDataList) {
            String strDate = stockData.getDate();
//			Date date = DateUtil.parse(strDate);
            int currentYear = getYear(strDate);
            if (currentYear == year) {
                if (null == first)
                    first = stockData;
                last = stockData;
            }
        }
        assert first != null;
        return (last.getClosingPrice() - first.getClosingPrice()) / first.getClosingPrice();
    }

    /**
     * 计算某一年的趋势投资收益
     *
     * @param year    年份
     * @param profits 收益数据
     * @return float
     */
    private float getTrendIncome(int year, List<Profit> profits) {
        Profit first = null;
        Profit last = null;
        for (Profit profit : profits) {
            String strDate = profit.getDate();
            int currentYear = getYear(strDate);
            if (currentYear == year) {
                if (null == first)
                    first = profit;
                last = profit;
            }
            if (currentYear > year)
                break;
        }
        assert first != null;
        return (last.getValue() - first.getValue()) / first.getValue();
    }

    /**
     * 计算完整时间范围内，每一年的股票投资收益和趋势投资收益
     *
     * @param stockDataList 股票数据
     * @param profits       收益数据
     * @return java.util.List<com.data.pojo.AnnualProfit>
     */
    private List<AnnualProfit> calculateAnnualProfits(List<StockData> stockDataList, List<Profit> profits) {
        List<AnnualProfit> result = new ArrayList<>();
        String strStartDate = stockDataList.get(0).getDate();
        String strEndDate = stockDataList.get(stockDataList.size() - 1).getDate();
        Date startDate = DateUtil.parse(strStartDate);
        Date endDate = DateUtil.parse(strEndDate);
        int startYear = DateUtil.year(startDate);
        int endYear = DateUtil.year(endDate);
        for (int year = startYear; year <= endYear; year++) {
            AnnualProfit annualProfit = new AnnualProfit();
            annualProfit.setYear(year);
            float indexIncome = getStockIncome(year, stockDataList);
            float trendIncome = getTrendIncome(year, profits);
            annualProfit.setStockIncome(indexIncome);
            annualProfit.setTrendIncome(trendIncome);
            result.add(annualProfit);
        }
        return result;
    }

    /**
     * 简单计算推荐等级
     *
     * @param end 最新股价
     * @param maAvg 均线值
     * @return java.lang.Integer
     */
    public Integer calculateRecommendedLevel(float end, float maAvg) {
        // 差值计算
        float deviationCalculation = maAvg / end;
        // 当均线高于最新股价，不推荐买入
        if (deviationCalculation > 1) {
            return 0;
        }
        // 当均线低于最新股价，根据程度决定推荐等级
        else {
            if (deviationCalculation > 0.8) {
                return 1;
            }
            if (deviationCalculation > 0.6) {
                return 2;
            }
            if (deviationCalculation > 0.4) {
                return 3;
            }
            if (deviationCalculation > 0.2) {
                return 4;
            }
            if (deviationCalculation > 0) {
                return 5;
            }
        }
        return 0;
    }

}
